/** uupaa-virtualtimer.js
 *
 * uupaa-virtualtimer.js is virtual timer support javascript library
 *  - uupaa.js spin-off project
 *
 * @author Takao Obara <uupaa.js@gmail.com>
 * @license uupaa-virtualtimer.js is licensed under the terms and conditions of the MIT licence.
 * @version 0.1
 * @date 2008-12-10
 * @see <a href="http://code.google.com/p/uupaa-js/">uupaa.js Home(Google Code)</a>
 * @see <a href="http://code.google.com/p/uupaa-js-spinoff/">uupaa.js SpinOff Project Home(Google Code)</a>
 */
if (!uuClass.VirtualTimer) {

uuClass.VirtualTimer = function() {
  this.construct.call(this);
};

// VirtualTimer factory
uu.vtm = function() {
  return new uuClass.VirtualTimer();
};

// --- const ---
uuConst.VirtualTimer = {
  State: {
    UNKNOWN: 0, // diet
    UNSETED: 1, // diet
    RUNNING: 2,
    LOOPOUT: 3,
    SUSPEND: 4
  }
};

/** Virtual Timer
 *
 * @class
 */
uuClass.VirtualTimer.prototype = {
  construct:
            function() {
              this._btid = -1; // base timer ID
              this._lock = 0;  // 1: diet
              this._db = [ /* vtid: { next, unset, fn, count, dfn, delay, last, loop, title, me }, ... */ ];
              var me = this;
              if (uuClass.Detect.ie) {
                window.attachEvent("onunload", function() {
                  me.suspend(-1); me.db = null;
                });
              } else {
                window.addEventListener("unload", function() {
                  me.suspend(-1); me.db = null;
                }, false);
              }
            },
  // uuClass.VirtualTimer.set - regist VirtualTimer
  set:      function(fn, delay, loop /* = -1 (INFINITY) */, title /* = "-" */, me /* = undefined */) {
              loop = (loop === void 0) ? -1 : loop;

              var dfn  = (typeof delay === "function") ? delay : null,
                  next = dfn ? dfn(0, title) : delay, // create first delay
                  vtid = this._db.length;             // create virtual timer id

              if (next < 0) { return -2; } // quit
              this.resume(-1); // base timer auto restart

              this._db[vtid] = {
                next:   (new Date | 0) + next, // next tick(0 is finished)
                last:   0,              // last tick
                unset:  0,              // unset flag
                fn:     fn,             // callback function
                count:  0,              // execution count
                dfn:    dfn,            // generation function of delay time
                delay:  delay,          // delay time
                loop:   loop,           // limited loop count
                title:  title || "-",   // identification string
                me:     me              // execution context
              };
              !loop && (this._db[vtid].next = 0); // loop=0 is Limited loop + finished state

              return vtid;
            },
  // uuClass.VirtualTimer.unset - unregist VirtualTimer
  unset:    function(vtid) {
              if (!(vtid in this._db)) { return; }
              var v = this._db[vtid];
              v.unset = 1;
            },
  // uuClass.VirtualTimer.extend - extend loop count
  extend:   function(vtid, loop /* = -1 */) {
              loop = (loop === void 0) ? -1 : loop;

              if (!(vtid in this._db) || this._db[vtid].unset) { return; }

              var v = this._db[vtid];
              if (loop < 0) { // loop < 0 to Infinity loop
                v.loop = -1;
              } else if (v.loop < 0) { // loop >= 0 to Limited loop
                v.loop = loop;
              } else { // add loop count
                v.loop = v.loop + loop;
              }
              v.next += !v.next ? 1 : 0; // restart if suspended
            },
  // uuClass.VirtualTimer.resume - resume
  resume:   function(vtid /* = -1 */) {
              vtid = (vtid === void 0) ? -1 : vtid;

              if (vtid < 0) {
                if (this._btid === -1) {
                  this._runner(); // restart
                }
              } else if (vtid in this._db) {
                var v = this._db[vtid];
                if (v.unset) { return; }
                v.next = 1; // restart
                (this._btid === -1) && this.resume(-1); // resume(-1) - restart base timer
              }
            },
  // uuClass.VirtualTimer.suspend - suspend
  suspend:  function(vtid /* = -1 */) {
              vtid = (vtid === void 0) ? -1 : vtid;

              if (vtid < 0) {
                if (this._btid !== -1) {
                  clearTimeout(this._btid);
                }
                this._btid = -1; // stop base timer
              } else if (vtid in this._db) {
                var v = this._db[vtid];
                if (v.unset) { return; }
                v.next = 0; // stop
              }
            },
  // uuClass.VirtualTimer.diet - memory compaction
  diet:     function() {
              ++this._lock;
            },
  _diet:    function() {
              var rv = [], i = 0, iz = this._db.length;
              for (; i < iz; ++i) {
                (this.state(i) & 0x6) && rv.push(this._db[i]);
              }
              this._db = rv;  // swap array
              this._lock = 0; // unlock
            },
  // uuClass.VirtualTimer.state - get state
  state:    function(vtid) {
              var _c = uuConst.VirtualTimer.State, v;
              if (vtid in this._db) {
                v = this._db[vtid];
                if (v.unset) { return _c.UNSETED; }
                if ( v.loop &&  v.next) { return _c.RUNNING; }
                if (!v.loop && !v.next) { return _c.LOOPOUT; }
                if ( v.loop && !v.next) { return _c.SUSPEND; }
              }
              return _c.UNKNOWN;
            },
  _runner:  function() {
              var solver = this;
              (function VTM_RUNNER() {
                var me = solver, tick = new Date | 0,
                    db = me._db, i = 0, iz = db.length, delay, v, next;

                me._lock && me._diet();

                for (; i < iz; ++i) {
                  v = db[i]; // fetch
                  next = v.next;
                  if (next && tick >= next) {

                    if (v.loop < 0 || --v.loop) { // rest
                      if (v.dfn) {
                        delay = v.dfn.call(v.me, ++v.count, v.title);
                        if (delay < 0) { // loopout
                          v.next = 0;
                          v.loop = 0;
                        } else {
                          v.next = v.last + delay;
                        }
                      } else {
                        v.next = v.last + v.delay;
                      }
                    } else {
                      v.next = 0; // loop out
                    }
                    v.last = tick;
                    v.fn.call(v.me);
                  }
                }
                me._btid = setTimeout(VTM_RUNNER, 0);
              })();
            }
};

} // if (!uuClass.VirtualTimer)

